/* 
 * FindObject.m
 *
 * Purpose:
 *		This object controls the searching for strings in a matrix or in a text object.
 *
 * You may freely copy, distribute, and reuse the code in this example.
 * NeXT disclaims any warranty of any kind, expressed or  implied, as to its
 * fitness for any particular use.
 *
 * Written by: Mary McNabb
 * Created: Apr 91
 *
 */
#import "FindObject.h"

#import <libc.h>
#import <strings.h>
#import <appkit/publicWraps.h>
#import <streams/streams.h>
#import <appkit/Form.h>
#import <appkit/Button.h>
#import <appkit/Matrix.h>
#import <appkit/Text.h>
#import <appkit/Window.h>
#import <appkit/Application.h>

#import "cUtils.h"

@implementation FindObject

/* initialize FindObject */
- init
{
	[super init];
	[NXApp loadNibSection:"FindPanel.nib" owner:self];
	return self;
}

/*
 *	This method sets the local variable searchMe.
 *	searchMe must be either a text object or a
 *	matrix of text fields.
 */
- setSearchMe:object
{
	searchMe = object;
	return self;
}

/*
 *	Order the findPanel front and select the search string.
 */
- findPanel:sender
{
	[findPanel makeKeyAndOrderFront:NULL];
	[findForm selectTextAt:0];
	return self;
}

/*
 *	This method is called when the user types carriage
 *	return in the textfield in the find panel.
 */
- findNextReturn:sender
{
	[findNextButton performClick:NULL];
	[findPanel orderOut:NULL];
	return self;
}

/*
 *	This method is called when the user pushes the next
 *	button on the find panel. It decides if the searchMe
 *	is of type matrix or text and calls subsequent methods
 *	accordingly.
 */
- findNext:sender
{
	const char *findStr =  [findForm stringValueAt:0];
	if (!strlen(findStr) || !searchMe) {
		NXBeep();
		return self;
	}
	ignoreCase = [ignoreCaseButton state];

	if ([searchMe isKindOf:[Matrix class]]) {
		[self findInMatrix:FORWARD :findStr];
	} else {
		[self findInText:FORWARD];
	}
	return self;
}

/*
 * This method is called when the user pushes the previous
 * button on the find panel.  It decides if the searchMe is of type
 * matrix or text and calls subsequent methods accordingly.
 */
- findPrevious:sender
{
	const char *findStr =  [findForm stringValueAt:0];

	if (!strlen(findStr) || !searchMe) {
		NXBeep();
		return self;
	}
	if ([searchMe isKindOf:[Matrix class]]) {
		[self findInMatrix:BACKWARD :findStr];
	} else {
		[self findInText:BACKWARD];
	}
	return self;
}

/*
 * Searches through a text object (also wraps around if necessary).
 */
- findInText:(int)direction
{
	char	*text, *textStart, *match;
	NXSelPt	start, end;
	int		count, newStart;
	NXStream	*stream;
    
	text = (char *)[findForm stringValueAt:0];

	[searchMe getSel:&start :&end];
	if (start.cp == end.cp) start.cp = end.cp = 0;

/* We need to set up a stream for the search */	    
	stream = [searchMe stream];
	if (!stream) {
		NXBeep();
		return self;
	}

/* find out how long the text is, and position ourselves either from the beginning or from
 * the selection position */
	NXSeek(stream, 0, NX_FROMEND);
	count = NXTell(stream);
	NXSeek(stream, 0, NX_FROMSTART);
	textStart = (char *)malloc(count + 1);
	NXRead(stream, textStart, count);
	*(textStart + count) = '\0';
	
	if (direction == FORWARD)
		match = textInString(text, textStart + end.cp, ignoreCase);
	else
		match = textInStringReverse(text, textStart + start.cp + strlen(text) - 1,
				textStart, ignoreCase);
				
/* wraparound if necessary */
	if (!match) {
		if (direction == FORWARD)
			match = textInString(text, textStart, ignoreCase);
		else
			match = textInStringReverse(text, textStart + strlen(textStart), textStart, ignoreCase);
	}

/* did we find anything? */    
	newStart = match - textStart;
	free(textStart);
	if (!match || (newStart == start.cp && (end.cp - start.cp) == strlen(text))) {
		NXBeep();
		return self;
	}

	[[searchMe setSel:newStart :newStart + strlen(text)]
    							scrollSelToVisible];
    
	return self;
}

- (BOOL)searchMatrixStrings:(int)start:(int)stop:(int)dir:(const char *)findStr
{
	int i;

	for (i = start; dir == FORWARD? i <= stop: i >= stop; i += dir)
	{
		id myCell = [searchMe cellAt:i :0];
		char *myContents = (char *) [myCell stringValue];
 		if (textInString((char *)findStr, myContents, ignoreCase))
		{	/* then we've got a match */
			[searchMe selectCellAt: i :0];
			return YES;
		}
	}
	return NO;
}

- findInMatrix:(int) direction :(const char*) findStr
{
	int stop, temp, start;
	if (direction == FORWARD) {
		[searchMe getNumRows:&stop numCols:&temp];
		stop--;
		start = [searchMe selectedRow] + 1;
	} else {
		stop = 0;
		start = [searchMe selectedRow]  - 1;
	}
	if (![self searchMatrixStrings:start :stop :direction :findStr])
	{	/* implements wrap-around search */
		if (direction == FORWARD) {
			start = 0;
			stop = [searchMe selectedRow];
		} else {
			[searchMe getNumRows:&start numCols:&temp];
			start -= 1;
			stop = [searchMe selectedRow] + 1;
		}
		if (![self searchMatrixStrings:start :stop :direction :findStr])
		{
			NXBeep();
			return self;
		}
	}
	return self;
}

@end
